package com.zkh.myutils.code;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.zkh.myutils.io.IOUtils;
import com.zkh.myutils.utils.ArrayUtils;
import com.zkh.myutils.utils.Regexp;
import com.zkh.myutils.utils.Utils;
import com.zkh.myutils.utils.UtilsException;

public class CoderUtils {
	
	/**
	 * 字符集
	 */
	private static final char[] CHARSET = {
		'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
		'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
		'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
		'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 
		'o','p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 
		'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', 
		'8', '9', '+', '/'
	};
	
	/**
	 * 字符集（适应在URL上传递的变形）
	 */
	private static final char[] CHARSET_URL = {
		'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
		'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
		'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
		'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 
		'o','p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 
		'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', 
		'8', '9', '-', '_'
	};
	
	/**
	 * CHARSET字符集每个字符对应在ASCII码中的位置。
	 */
	private static final int[] ASCII = {
    	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    	-1, -1, -1, 62, -1, -1, -1, 63, 52, 53,
    	54, 55, 56, 57, 58, 59, 60, 61, -1, -1,
    	-1, -1, -1, -1, -1, 0 , 1 , 2 , 3 ,  4,
    	5 , 6 , 7 , 8 , 9 , 10, 11, 12, 13, 14,
    	15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
    	25, -1, -1, -1, -1, -1, -1, 26, 27, 28,
    	29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
    	39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
    	49, 50, 51
	};
 	
 	/**
 	 * CHARSET字符集每个字符对应在ASCII码中的位置。
 	 */
	private static final int[] ASCII_URL = {
     	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
     	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
     	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
     	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
     	-1, -1, -1, -1, -1, 62, -1, -1, 52, 53,
     	54, 55, 56, 57, 58, 59, 60, 61, -1, -1,
     	-1, -1, -1, -1, -1, 0 , 1 , 2 , 3 ,  4,
     	5 , 6 , 7 , 8 , 9 , 10, 11, 12, 13, 14,
     	15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
     	25, -1, -1, -1, -1, 63, -1, 26, 27, 28,
     	29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
     	39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
     	49, 50, 51
	};
	
	/**
	 * 中文正则
	 */
	private static final Pattern CHINESE_PATTERN = Pattern.compile("[\u4e00-\u9fa5]");
	/**
	 * 中文UNICODE正则
	 */
	private static final Pattern CHINESE_UNICODE_PATTERN = Pattern.compile("\\\\u([0-9a-fA-F]{4})");
	
	/**
	 * Base64编码。若要将编码后的字符串当作URL参数传递，请调用encode(String src, true)方法
	 * @param src 需要编码的字符串
	 * @return
	 */
	public static String encodeBase64(String src) {
		return encodeBase64(src, false);
	}
	
	/**
	 * Base64编码。若要将编码后的字符串当作URL参数传递，请调用encode(String src, true)方法
	 * @param src 需要编码的字符串
	 * @return
	 */
	public static String encodeBase64(byte[] src) {
		return encodeBase64(src, false);
	}
	
	/**
	 * Base64编码
	 * @param src 需要编码的字符串
	 * @param isURL 是否用于URL传递参数
	 * @return
	 */
	public static String encodeBase64(String src, boolean isURL) {
		if(src==null) {
			return null;
		}
		if(src.isEmpty()) {
			return "";
		}
		return encodeBase64(src.getBytes(Charsets.UTF_8), isURL);
	}
	
	/**
	 * 将流转换为base64
	 * @param is 输入流
	 * @return
	 * @throws IOException 
	 */
	public static String encodeBase64(InputStream is) throws IOException {
		//转为字节数组
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		//读取数据
		IOUtils.copyStream(is, baos);
		//base64编码
		return encodeBase64(baos.toByteArray());
	}
	
	/**
	 * base64编码
	 * @param src 需要编码的字节数组
	 * @param isURL 是否用于URL传递参数
	 */
	public static String encodeBase64(byte[] src, boolean isURL) {
		//选择相应的字符集
		char[] charset = isURL ? CHARSET_URL : CHARSET;
		//字节数组长度、按3个字节分组的分组数量、分组后剩余的字节数
		int len = src.length, groups = src.length/3, mod = len-groups*3;
		//字节数组索引
		int c = 0;
		//结果字符
		StringBuffer result = new StringBuffer();
		//遍历
		for (int i=0;i<groups;i++) {
			//获取分组内三个字符的位置并补位
			int first = src[c++] & 0xff;
			int second = src[c++] & 0xff;
			int third = src[c++] & 0xff;
			//按4个字节为一组，进行相应的移位操作并获取字符
			result.append(charset[first >> 2]);
			result.append(charset[(first << 4) & 0x3f | second >> 4]);
			result.append(charset[(second << 2) & 0x3f | third >> 6]);
			result.append(charset[third & 0x3f]);
		}
		//分组没有刚好分配完，进行补=操作
		if(mod!=0) {
			int first = src[c++] & 0xff;
			result.append(charset[first >> 2]);
			if(mod==1) {
				result.append(charset[(first << 4) & 0x3f]);
				result.append("==");
			}else{
				int second = src[c++] & 0xff;
				result.append(charset[(first << 4) & 0x3f | second >> 4]);
				result.append(charset[(second << 2) & 0x3f]);
				result.append("=");
			}
		}
		return result.toString();
	}
	
	/**
	 * base64解码
	 * @param src 待解码的字符串
	 */
	public static String decodeBase64ToString(String src) {
		return decodeBase64ToString(src, false);
	}
	
	/**
	 * base64解码
	 * @param src 待解码的字符串
	 * @param isURL 加密时传递的isURL参数。若加密时没有传递isURL参数，则传递false。
	 */
	public static String decodeBase64ToString(String src, boolean isURL) {
		return new String(decodeBase64(src, isURL), Charsets.UTF_8);
	}
	
	/**
	 * base64解码
	 * @param src 待解码的字符串
	 */
	public static byte[] decodeBase64(String src) {
		return decodeBase64(src, false);
	}

	/**
	 * base64解码
	 * @param src 待解码的字符串
	 * @param isURL 加密时传递的isURL参数。若加密时没有传递isURL参数，则传递false。
	 */
	public static byte[] decodeBase64(String src, boolean isURL) {
		if(src==null) {
			return null;
		}
		//字符长度，4个一组分组
		int len = src.length(), groups = len/4;
		if(groups * 4 != len) {
			throw new IllegalArgumentException("String length must be a multiple of 4.");
		}
		int lastMissingNum = 0;
		int numFullGroup = groups;
		if(len != 0) {
			if(src.charAt(len - 1)=='=') {
				lastMissingNum++;
				numFullGroup--;
			}
			if(src.charAt(len - 2)=='=') {
				lastMissingNum ++;
			}
		}
		byte[] result = new byte[groups*3 - lastMissingNum];
		int charIndex = 0;
		int resultIndex = 0;
		for (int i = 0; i < numFullGroup; i++) {
			int char0 = base64Toint(src.charAt(charIndex++), isURL);
			int char1 = base64Toint(src.charAt(charIndex++), isURL);
			int char2 = base64Toint(src.charAt(charIndex++), isURL);
			int char3 = base64Toint(src.charAt(charIndex++), isURL);
			result[resultIndex++] = (byte)(char0 << 2 | char1 >> 4);
			result[resultIndex++] = (byte)(char1 << 4 | char2 >> 2);
			result[resultIndex++] = (byte)(char2 << 6 | char3);
		}
		if(lastMissingNum != 0) {
			int char0 = base64Toint(src.charAt(charIndex++), isURL);
			int char1 = base64Toint(src.charAt(charIndex++), isURL);
			result[resultIndex++] = (byte)(char0 << 2 | char1 >> 4);
			if(lastMissingNum == 1) {
				int char2 = base64Toint(src.charAt(charIndex++), isURL);
				result[resultIndex++] = (byte)(char1 << 4 | char2 >> 2);
			}
		}
		return result;
	}
	
	/**
	 * 加密字符串。这是个可解密的加密方法，解密密钥由系统自动生成，为一个10位的数字字符串。
	 * @param src 需要加密的字符串
	 * @return [加密后的字符串，密钥]，不会返回null，加密字符串为null时，返回值第一个值为null。
	 */
	public static String[] encode(String src) {
		return encode(src, false);
	}
	
	
	/**
	 * 加密字符串。这是个可解密的加密方法，解密密钥由系统自动生成，为一个10位的数字字符串。
	 * @param src 需要加密的字符串
	 * @param isURL 是否用于URL传递参数
	 * @return [加密后的字符串，密钥]，不会返回null，加密字符串为null时，返回值第一个值为null。
	 */
	public static String[] encode(String src, boolean isURL) {
		//随机密钥
		StringBuffer key = new StringBuffer();
		//密钥
		byte[] keys = new byte[10];
		//生成密钥
		for(int i=0;i<10;i++) {
			keys[i] = (byte) Utils.rand(10);
			key.append(keys[i]);
		}
		//返回容器
		String[] rtn = {null, key.toString()};
		//不为null
		if(src!=null) {
			//加密
			byte[] bs = src.getBytes(Charsets.GBK);int len=bs.length;
			//转码容器
			String[] en = new String[len];
			//转码
			for(int i=0;i<len;i++) {
				en[i] = String.valueOf(bs[i] ^ keys[i%10]);
			}
			//保存
			rtn[0] = encodeBase64(String.join(",", en), isURL);
		}
		//返回值
		return rtn;
	}
	
	/**
	 * 解密字符串
	 * @param src 需解密的字符串
	 * @param key 密钥。调用encode()加密时返回数组的第二个值。
	 * @return
	 */
	public static String decode(String src, String key) {
		return decode(src, key, false);
	}
	
	/**
	 * 解密字符串
	 * @param src 需解密的字符串
	 * @param key 密钥。调用encode()加密时返回数组的第二个值。
	 * @param isURL 加密时传递的isURL参数。若加密时没有传递isURL参数，则传递false。
	 * @return
	 */
	public static String decode(String src, String key, boolean isURL) {
		if(!Regexp.isMatch("\\d{10}", key)) {
			throw new IllegalArgumentException("密钥格式错误");
		}
		if(src==null) {
			return null;
		}
		//解析密钥
		byte[] keys = new byte[10];
		int j = 0;
		for(char c: key.toCharArray()) {
			keys[j++] = Byte.parseByte(c + "");
		}
		//base64解码
		Integer[] en = ArrayUtils.convertType((CoderUtils.decodeBase64ToString(src, isURL)).split(","), Integer.class, e->Integer.valueOf(e));
		//长度
		int len=en.length;
		//解码后容器
		byte[] ens = new byte[len];
		//解码
		for(int i=0;i<len;i++) {
			ens[i] =  (byte) (en[i] ^ keys[i%10]);
		}
		//返回解码后的字符串
		return new String(ens, Charsets.GBK);
	}
	
	/**
	 * 根据字符查找在intToBase64中的位置
	 * @param c
	 * @return
	 */
     private static int base64Toint(char c, boolean isURL) {
		int index = isURL ? ASCII_URL[c] : ASCII[c];
		if(index<0) {
			throw new IllegalArgumentException("Illegal character " + c);
		}
		return index;
     }
 	
 	/**
 	 * URL以UTF-8编码解码
 	 * @param url 需要解码的URL
 	 * @return
 	 */
 	public static String decodeURL(String url){
 		return decodeURL(url, Charsets.UTF_8);
 	}
 	
 	/**
 	 * URL解码
 	 * @param url 需要解码的URL
 	 * @param code 编码
 	 * @return
 	 */
 	public static String decodeURL(String url, Charset code){
 		try {
 			if(url==null) return null;
 			return URLDecoder.decode(url, code.name());
 		} catch (UnsupportedEncodingException e) {
 			throw new UtilsException("不支持"+code.name()+"编码");
 		}
 	}
 	
 	/**
 	 * URL以UTF-8编码编码
 	 * @param url 需要编码的URL
 	 * @return
 	 */
 	public static String encodeURL(String url){
 		return encodeURL(url, Charsets.UTF_8);
 	}
 	
 	/**
 	 * URL编码
 	 * @param url 需要编码的URL
 	 * @param code 编码
 	 * @return
 	 */
 	public static String encodeURL(String url, Charset code){
 		try {
 			return URLEncoder.encode(url, code.name());
 		} catch (UnsupportedEncodingException e) {
 			throw new UtilsException("不支持"+code.name()+"编码");
 		}
 	}
	
	/**
	 * 将Unicode编码转换为中文字符
	 * @param unicodeStr Unicode编码字符
	 * @return 解析后的字符
	 */
	public static String decodeUnicode(String unicodeStr) {
		//为空
		if(unicodeStr==null) {
			return null;
		}
		//匹配Unicode编码的正则
        Matcher m = CHINESE_UNICODE_PATTERN.matcher(unicodeStr);
        //依次替换
        while(m.find()) {
        	unicodeStr = unicodeStr.replace(m.group(), String.valueOf((char) Integer.parseInt(m.group(1), 16)));
        }
        return unicodeStr;
	}
	
	/**
	 * 将中文字符转换为Unicode编码
	 * @param chinese 中文
	 * @return 将中文字符转换为\ u开头的字符
	 */
	public static String encodeUnicode(String chinese) {
		if(chinese==null) {
			return null;
		}
		//匹配中文
		Matcher m = CHINESE_PATTERN.matcher(chinese);
        //遍历中文并处理
        while(m.find()){
            chinese = chinese.replace(m.group(), "\\u" + Integer.toHexString(m.group().charAt(0)));
        }
        return chinese;
	}
}
